﻿using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Sidvall.Collections;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Sidvall.Serialization
{
    public class DynamicContractResolver : DefaultContractResolver
    {
        #region Public Members

        #region DataTypePropertyItems

        private DataTypePropertyItemCollection _DataTypePropertyItems;
        public DataTypePropertyItemCollection DataTypePropertyItems
        {
            get
            {
                if (_DataTypePropertyItems == null)
                    return _DataTypePropertyItems = new DataTypePropertyItemCollection();
                return _DataTypePropertyItems;
            }
        }

        #endregion
        #region PropertyItems

        private PropertyItemCollection _PropertyItems;
        public PropertyItemCollection PropertyItems
        {
            get
            {
                if (_PropertyItems == null)
                    return _PropertyItems = new PropertyItemCollection();
                return _PropertyItems;
            }
        }

        #endregion

        #region CreateProperties

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            PropertyItem propertyItem;

            var properties = base.CreateProperties(type, memberSerialization);
            var entityProperties = (from o in this.PropertyItems
                                    where (string.IsNullOrWhiteSpace(o.EntityName)) || (o.EntityName.Equals(type.Name, StringComparison.OrdinalIgnoreCase))
                                    select o);
            var otherProperties = (from o in this.PropertyItems
                                   where (!string.IsNullOrWhiteSpace(o.EntityName)) && (!o.EntityName.Equals(type.Name, StringComparison.OrdinalIgnoreCase)) &&
                                       (o.PropertyItemType == PropertyItemType.ExcludeAllExceptThis)
                                   select o);
            var propertyIndex = entityProperties.ToDictionary(o => o.PropertyName, true, true, System.StringComparer.OrdinalIgnoreCase);

            // Initialize ignored properties
            var ignoredNames = (from o in entityProperties
                                where (o.PropertyItemType == PropertyItemType.Exclude)
                                select o.PropertyName).ToHashSet(o => o, true, System.StringComparer.OrdinalIgnoreCase);
            foreach (var otherProperty in otherProperties)
            {
                if (!ignoredNames.Contains(otherProperty.PropertyName))
                    ignoredNames.Add(otherProperty.PropertyName);
            }

            // Initialize properties
            foreach (var property in properties)
            {
                if (ignoredNames.Contains(property.PropertyName))
                {
                    property.Ignored = true;
                    continue;
                }
                if (propertyIndex.TryGetValue(property.PropertyName, out propertyItem))
                {
                    if (propertyItem.NullValueHandlingPolicy != null)
                    {
                        switch (propertyItem.NullValueHandlingPolicy.Value)
                        {
                            case NullValueHandlingPolicy.Ignore:
                                property.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
                                break;
                            default:
                                property.NullValueHandling = Newtonsoft.Json.NullValueHandling.Include;
                                break;
                        }
                    }
                    if (propertyItem.PropertyType != null)
                        property.PropertyType = propertyItem.PropertyType;
                    if (propertyItem.MemberConverter != null)
                        property.MemberConverter = new JsonConverter(propertyItem.MemberConverter, property.PropertyName);
                    if (propertyItem.ValueProvider != null)
                        property.ValueProvider = new ValueProvider(propertyItem.ValueProvider, property.PropertyName);
                    if (!string.IsNullOrWhiteSpace(propertyItem.NewPropertyName))
                        property.PropertyName = propertyItem.NewPropertyName;
                    switch (this.DefaultDisplayPositionPolicy)
                    {
                        case DisplayPositionPolicy.DisplayPosition:
                        case DisplayPositionPolicy.DisplayPositionAndName:
                            if (propertyItem.DisplayPosition != null)
                                property.Order = propertyItem.DisplayPosition;
                            else
                                property.Order = this.DefaultDisplayPosition;
                            break;
                        default:
                            break;
                    }
                }
                else
                {
                    var dataType = Sidvall.Data.DataTypeManager.GetDataType(property.PropertyType);
                    var dataTypePropertyItem = this.DataTypePropertyItems.GetItem(dataType);
                    if (dataTypePropertyItem != null)
                    {
                        if (dataTypePropertyItem.PropertyType != null)
                            property.PropertyType = dataTypePropertyItem.PropertyType;
                        if (dataTypePropertyItem.MemberConverter != null)
                            property.MemberConverter = new JsonConverter(dataTypePropertyItem.MemberConverter, property.PropertyName);
                        if (dataTypePropertyItem.ValueProvider != null)
                            property.ValueProvider = new ValueProvider(dataTypePropertyItem.ValueProvider, property.PropertyName);
                        switch (this.DefaultDisplayPositionPolicy)
                        {
                            case DisplayPositionPolicy.DisplayPosition:
                            case DisplayPositionPolicy.DisplayPositionAndName:
                                if (dataTypePropertyItem.DisplayPosition != null)
                                    property.Order = dataTypePropertyItem.DisplayPosition;
                                else
                                    property.Order = this.DefaultDisplayPosition;
                                break;
                            default:
                                break;
                        }
                    }
                    else
                    {
                        switch (this.DefaultDisplayPositionPolicy)
                        {
                            case DisplayPositionPolicy.DisplayPosition:
                            case DisplayPositionPolicy.DisplayPositionAndName:
                                property.Order = this.DefaultDisplayPosition;
                                break;
                            default:
                                break;
                        }
                    }
                }
            }

            // Return list
            switch (this.DefaultDisplayPositionPolicy)
            {
                case DisplayPositionPolicy.DisplayPosition:
                    return (from o in properties
                            where !o.Ignored
                            orderby o.Order
                            select o).ToList();
                case DisplayPositionPolicy.DisplayPositionAndName:
                    return (from o in properties
                            where !o.Ignored
                            orderby o.Order, o.PropertyName
                            select o).ToList();
                case DisplayPositionPolicy.Name:
                    return (from o in properties
                            where !o.Ignored
                            orderby o.PropertyName
                            select o).ToList();
                default:
                    return (from o in properties
                            where !o.Ignored
                            select o).ToList();
            }
        }

        #endregion

        #endregion
        #region Private Members

        private DisplayPositionPolicy DefaultDisplayPositionPolicy { get; set; }
        private int? DefaultDisplayPosition { get; set; }

        #region JsonConverter

        private class JsonConverter : Newtonsoft.Json.JsonConverter
        {
            #region Public Members

            #region CanRead

            public override bool CanRead
            {
                get
                {
                    return true;
                }
            }

            #endregion
            #region CanWrite

            public override bool CanWrite
            {
                get
                {
                    return true;
                }
            }

            #endregion
            #region CanConvert

            public override bool CanConvert(Type objectType)
            {
                return true;
            }

            #endregion
            #region WriteJson

            public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, JsonSerializer serializer)
            {
                var jsonWriter = new JsonWriter(writer);
                this.InternalJsonConverter.WriteJson(this.PropertyName, jsonWriter, value);
            }

            #endregion
            #region ReadJson

            public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
            {
                var jsonReader = new JsonReader(reader);
                return this.InternalJsonConverter.ReadJson(this.PropertyName, jsonReader, objectType, existingValue);
            }

            #endregion

            #endregion
            #region Private Members

            private Sidvall.Serialization.IJsonConverter InternalJsonConverter { get; set; }
            private string PropertyName { get; set; }

            #endregion
            #region Constructors

            public JsonConverter(Sidvall.Serialization.IJsonConverter jsonConverter, string propertyName)
            {
                this.InternalJsonConverter = jsonConverter;
                this.PropertyName = propertyName;
            }

            #endregion
        }

        #endregion
        #region JsonReader 

        private class JsonReader : IJsonReader
        {
            #region Public Members

            #region Path

            public string Path
            {
                get
                {
                    return this.InternalJsonReader.Path;
                }
            }

            #endregion
            #region Value

            public object Value
            {
                get
                {
                    return this.InternalJsonReader.Value;
                }
            }

            #endregion

            #endregion
            #region Private Members

            private Newtonsoft.Json.JsonReader InternalJsonReader { get; set; }

            #endregion
            #region Constructors

            public JsonReader(Newtonsoft.Json.JsonReader jsonReader)
            {
                this.InternalJsonReader = jsonReader;
            }

            #endregion
        }

        #endregion
        #region JsonWriter

        private class JsonWriter : IJsonWriter
        {
            #region Public Members

            #region WriteValue

            public void WriteValue(object value)
            {
                this.InternalJsonWriter.WriteValue(value);
            }

            #endregion

            #endregion
            #region Private Members

            private Newtonsoft.Json.JsonWriter InternalJsonWriter { get; set; }

            #endregion
            #region Constructors

            public JsonWriter(Newtonsoft.Json.JsonWriter jsonWriter)
            {
                this.InternalJsonWriter = jsonWriter;
            }

            #endregion
        }

        #endregion
        #region ValueProvider

        private class ValueProvider : Newtonsoft.Json.Serialization.IValueProvider
        {
            #region Public Members

            #region GetValue

            public object GetValue(object target)
            {
                return this.InternalValueProvider.GetValue(this.PropertyName, target);
            }

            #endregion
            #region SetValue

            public void SetValue(object target, object value)
            {
                this.InternalValueProvider.SetValue(this.PropertyName, target, value);
            }

            #endregion

            #endregion
            #region Private Members

            private Sidvall.Serialization.IValueProvider InternalValueProvider { get; set; }
            private string PropertyName { get; set; }

            #endregion
            #region Constructors

            public ValueProvider(Sidvall.Serialization.IValueProvider valueProvider, string propertyName)
            {
                this.InternalValueProvider = valueProvider;
                this.PropertyName = propertyName;
            }

            #endregion
        }

        #endregion

        #endregion
        #region Constructors

        public DynamicContractResolver()
        {
        }
        public DynamicContractResolver(IEnumerable<PropertyItem> propertyItems, DataTypePropertyItemCollection dataTypePropertyItems,
            DisplayPositionPolicy defaultDisplayPositionPolicy, int? defaultDisplayPosition)
        {
            _DataTypePropertyItems = new DataTypePropertyItemCollection(dataTypePropertyItems);
            _PropertyItems = new PropertyItemCollection(propertyItems);
            this.DefaultDisplayPositionPolicy = defaultDisplayPositionPolicy;
            this.DefaultDisplayPosition = defaultDisplayPosition;
        }

        #endregion
    }
}
